package liming.tool.handle.net.udp.socket;

import liming.tool.handle.FileRW;
import liming.tool.handle.net.udp.base.UdpServer;
import liming.tool.handle.net.udp.bean.UdpHandleObject2;
import liming.tool.handle.net.udp.bean.UdpNetObject;
import liming.tool.pool.ListPool;
import liming.tool.pool.ListenPool;
import liming.tool.pool.Pools;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public abstract class UdpServerSocket extends UdpSocket implements UdpServer {

    private final int BACK_LOG, HAND_SIZE, NUM;//监听端口数量  辅助处理池个数 缓存数据最大个数
    private final long TIME_OUT;//
    private final List<DatagramSocket> sockets;//所监听的端口
    private final ListenPool<UdpHandleObject2>[] udpNetObjectListenPools;//分处理池
    private final ScheduledExecutorService executorService;//监听业务处理池
    private final Lock lock;
    private final Map<String, Integer> valuesRun;// 关键字所在的处理线程池序号
    private final int[] valuesWait; // 线程池待处理的数据包数
    private ListPool<DatagramPacket> datagramPacketListPool;//对象缓冲池

    /**
     * 默认构造函数，在网络通信中，套接字信息都是一个必要的参数
     *
     * @param backlog        监听端口个数 在生成后不可更改
     * @param datagramSocket Udp监听端口
     * @param handSize       辅助处理池个数
     */
    public UdpServerSocket(DatagramSocket datagramSocket, int backlog, int handSize, long timeOut) {
        this(datagramSocket, backlog, handSize, timeOut, 5000, SIZE, 1);
    }

    public UdpServerSocket(InetAddress inetAddress, int port) throws SocketException {
        this(new DatagramSocket(port, inetAddress), 20, 5, 0);
    }

    public UdpServerSocket(DatagramSocket datagramSocket) {
        this(datagramSocket, 20, 5, 0);
    }

    /**
     * @param datagramSocket 监听套接字
     * @param backlog        最大监听套接字数量
     * @param handSize       辅助处理池个数
     * @param timeOut        超时判定时间，通常为0
     * @param tempNum        接收DatagramPacket时缓存的数量
     * @param tempSize       接收DatagramPacket时每个缓存的大小
     * @param handNum        提交完整数据时处理线程的个数
     */
    public UdpServerSocket(DatagramSocket datagramSocket, int backlog, int handSize, long timeOut, int tempNum, int tempSize, int handNum) {
        super(datagramSocket, handNum);
        lock = new ReentrantLock();
        valuesRun = new HashMap<>();
        BACK_LOG = backlog;
        HAND_SIZE = handSize;
        TIME_OUT = timeOut;
        NUM = tempNum;
        SIZE = tempSize;
        valuesWait = new int[handSize];
        udpNetObjectListenPools = new ListenPool[handSize];
        sockets = new ArrayList<>(backlog);
        executorService = Executors.newScheduledThreadPool(backlog);
        initPools();
        add(datagramSocket);
    }

    @Override
    public boolean handleAfterPacketData(DatagramPacket datagramPacket, DatagramSocket datagramSocket, UdpNetObject udpNetObject) throws Exception {
        datagramPacketListPool.put(datagramPacket);
        return super.handleAfterPacketData(datagramPacket, datagramSocket, udpNetObject);
    }

    @Override
    public UdpNetObject handleDataReceiveListenPool(UdpNetObject udpNetObject, DatagramSocket datagramSocket) throws Exception {
        String ID;
        ID = udpNetObject.getId();
        int i = -1;
        /* 获取当前key在缓冲列表位置，若存在则加入，若不存在则计算最小的那个缓冲位置 */
        lock.lock();
        try {
            if (valuesRun.containsKey(ID)) {
                i = valuesRun.get(ID);
            } else {
                i = 0;
                for (int n = 0; n < HAND_SIZE; n++) {
                    if (valuesWait[i] > valuesWait[n])
                        i = n;
                }
            }
            valuesRun.put(ID, i);
            udpNetObjectListenPools[i].put(new UdpHandleObject2(udpNetObject, datagramSocket));
            add(i);
        } finally {
            lock.unlock();
        }
        return udpNetObject;
    }

    @Override
    public boolean handleDataSubmissionPool(UdpReceiveMap udpReceiveMap, DatagramSocket datagramSocket) throws Exception {
        if (super.handleDataSubmissionPool(udpReceiveMap, datagramSocket)) {
            UdpReceiveMap response = new UdpReceiveMap();
            if (doWork(udpReceiveMap, response)) {
                send(datagramSocket, response, udpReceiveMap.getInetAddress(), udpReceiveMap.getPort());
            }
        }
        return false;
    }

    private void initPools() {
        //初始化 datagramPacket 对象缓冲池
        datagramPacketListPool = Pools.getListPool("System.udp.temp.datagramPacket@" + hashCode(), NUM);
        while (!datagramPacketListPool.isFull()) {
            try {
                datagramPacketListPool.put(new DatagramPacket(new byte[SIZE], SIZE));
            } catch (Exception e) {
                break;
            }
        }
        //初始化接收端数据接收分处理池
        for (int i = 0; i < HAND_SIZE; i++) {
            udpNetObjectListenPools[i] = new ListenPool<UdpHandleObject2>(Pools.getListPool("System.udp.server@" + hashCode() + "_" + i), 0, 0) {
                @Override
                public void handle(UdpHandleObject2 udpHandleObject2) throws Exception {
                    UdpNetObject addUdpNetObject = udpHandleObject2.getUdpNetObject();
                    try {
                        UdpServerSocket.super.handleDataReceiveListenPool(udpHandleObject2.getUdpNetObject(), udpHandleObject2.getDatagramSocket());
                    } finally {
                        minus(addUdpNetObject.getId());
                    }
                }
            };
        }
    }


    private void minus(String key) {
        minus(valuesRun.get(key));
    }

    /**
     * 用于计数减
     *
     * @param i 池子编号
     */
    private void minus(int i) {
        lock.lock();
        try {
            valuesWait[i]--;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 用于计数加
     *
     * @param i 池子编号
     */
    private void add(int i) {
        lock.lock();
        try {
            valuesWait[i]++;
        } finally {
            lock.unlock();
        }
    }

    public void send(UdpReceiveMap udpReceiveMap, InetAddress inetAddress, int port) throws IOException, ClassNotFoundException {
        if (sockets.size() == 0) {
            send(new DatagramSocket(), udpReceiveMap, inetAddress, port);
        } else
            send(sockets.get(0), udpReceiveMap, inetAddress, port);
    }

    public void add(DatagramSocket datagramSocket) {
        if (sockets.size() < BACK_LOG) {
            sockets.add(datagramSocket);
            executorService.scheduleAtFixedRate(() -> {
                String ip = datagramSocket.getLocalAddress() + ":" + datagramSocket.getLocalPort();
                while (!datagramSocket.isClosed()) {
                    try {
                        DatagramPacket datagramPacket = datagramPacketListPool.getWait(1000);
                        datagramSocket.receive(datagramPacket);
                        put(datagramPacket, datagramSocket);
                    } catch (SocketException ignored) {
                        writeStrongLog(ip + " 停止监听");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, 0, Math.max(TIME_OUT, 1), TimeUnit.MILLISECONDS);
        }
    }

    @Override
    public UdpReceiveMap addReceiveMap(UdpReceiveMap request) throws Exception {
        UdpReceiveMap respond = new UdpReceiveMap();
        try {
            doWork(request, respond);
        } catch (Exception e) {
            respond.removeAll();
            respond.setMassage(FileRW.getError(e));
        } catch (Error e) {
            respond.removeAll();
            respond.setMassage(FileRW.getError(e));
        }
        return respond;
    }

    @Override
    public void close() throws IOException {
        super.close();
        executorService.shutdownNow();
        for (ListenPool<?> listenPool : udpNetObjectListenPools) {
            listenPool.close();
        }
        for (DatagramSocket socket : sockets) socket.close();
        System.exit(0);
    }

    @Override
    public String toString() {
        return "UDPServerSocket{" +
                "BACK_LOG=" + BACK_LOG +
                ", HAND_SIZE=" + HAND_SIZE +
                ", TIME_OUT=" + TIME_OUT +
                ", sockets=" + sockets +
                ", datagramPacketListPool=" + datagramPacketListPool +
                ", executorService=" + executorService +
                '}';
    }
}
